<!--
Copyright 2019-2021 Huawei Technologies Co., Ltd.All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<template>
  <!--cl-graph-manage -->
  <div class="cl-graph-manage">
    <div class="graph-p32">
      <div class="guide-content"
           v-if="guide.show">
        <el-popover placement="top-start"
                    ref="popover"
                    :style="{ position: 'absolute', top: guide.top, left: guide.left }"
                    :title="guide.title"
                    width="370"
                    v-model="guide.show">
          <i class="el-icon-close"
             @click="closeUserGuide"></i>
          <div v-for="item in guide.content"
               :key="item"
               class="guide-span">{{ item }}</div>
          <div class="step-pic">
            <img :src="require(`@/assets/images/${themeIndex}/graph-stepimg${guide.step}.svg`)" />
          </div>

          <el-button type="primary"
                     @click="guideNext">{{
            guide.step === 3 ? $t('graph.finish') : $t('graph.next')
          }}</el-button>
        </el-popover>
        <div class="step"
             v-show="guide.step == 1">
          <img :src="require(`@/assets/images/${themeIndex}/graph-step1${language === 'en-us' ? '-en' : ''}.svg`)"
               alt="" />
        </div>
        <div class="step"
             v-show="guide.step == 2">
          <img :src="require(`@/assets/images/${themeIndex}/graph-step2${language === 'en-us' ? '-en' : ''}.svg`)"
               alt="" />
        </div>
        <div class="step"
             v-show="guide.step == 3">
          <img :src="require(`@/assets/images/${themeIndex}/graph-step3${language === 'en-us' ? '-en' : ''}.svg`)"
               alt="" />
        </div>
      </div>
      <div class="cl-title cl-graph-title">
        <div class="cl-title-left">
          {{ $t('graph.titleText') }}
          <div class="path-message">
            <span>{{$t('symbols.leftbracket')}}</span>
            <span>{{$t('trainingDashboard.summaryDirPath')}}</span>
            <span>{{summaryPath}}</span>
            <span>{{$t('symbols.rightbracket')}}</span>
          </div>
          <span @click="showUserGuide"
                class="guide">
            <i class="guide-icon"
               :class="`guide-icon-${themeIndex}`"></i>
            {{$t('graph.guide')}}
          </span>
        </div>
        <div class="cl-title-right">
          <div class="cl-close-btn"
               @click="jumpToTrainDashboard">
            <img src="@/assets/images/close-page.png" />
          </div>
        </div>
      </div>
      <div class="cl-content">
        <div id="graphs">
          <div class="cl-graph"
               :class="fullScreen ? 'full-screen' : ''">
            <!-- graph -->
            <div class="graph-container"
                 :class="rightShow ? '' : 'all'">
              <!-- No data is displayed. -->
              <div class="image-noData"
                   v-if="!loading.show && !Object.keys(allGraphData).length">
                <div>
                  <img :src="require('@/assets/images/nodata.png')"
                       alt="" />
                </div>
                <div class="noData-text">{{ $t('public.noData') }}</div>
              </div>
              <div :class="{
                     'graph-mode-switch': true,
                     'graph-mode-switch-full-screen': fullScreen,
                   }">
                <el-tooltip placement="bottom"
                            effect="light">
                  <div slot="content"
                       class="tooltip-container">
                    <div class="cl-graph-sidebar-tip">
                      {{ $t('graph.optimizeText') }}
                    </div>
                  </div>
                  <el-switch v-model="isOptimizeGraph"
                             @change="handleGraphModeChange" />
                </el-tooltip>
                {{ $t('components.optimize') }}
              </div>
              <!-- Operation button column -->
              <div class="operate-button-list">
                <!-- Download button -->
                <div :title="$t('graph.fullScreen')"
                     class="full-screen-button"
                     @click="toggleScreen"></div>
                <div :title="$t('graph.downloadPic')"
                     class="download-button"
                     @click="downLoadSVG"></div>
              </div>
              <div id="graph"
                   class="graph"
                   v-loading.fullscreen.lock="loading.show"
                   element-loading-background="rgba(0, 0, 0, 0.3)"
                   :element-loading-text="loading.info"></div>
            </div>
            <!-- Right column -->
            <div id="sidebar"
                 :class="rightShow ? '' : 'right-hide'">
              <div class="toggle-right"
                   @click="toggleRight"
                   :class="[rightShow?'':'toggle-left',`toggle-${themeIndex}-btn`]">
              </div>
              <!-- Search box -->
              <div class="sidebar-tooltip">
                <el-tooltip placement="top"
                            effect="light">
                  <div slot="content"
                       class="tooltip-container">
                    <div class="cl-graph-sidebar-tip">
                      {{$t('graph.sidebarTip')}}
                    </div>
                  </div>
                  <i class="el-icon-info"></i>
                </el-tooltip>
              </div>
              <el-select @change="fileChange"
                         @visible-change="getSelectList"
                         :popper-append-to-body="false"
                         class="search"
                         v-model="fileSearchBox.value">
                <el-option v-for="item in fileSearchBox.suggestions"
                           :key="item.value"
                           :title="item.value"
                           :label="item.value"
                           :value="item.value">
                </el-option>
              </el-select>
              <!-- Search box -->
              <div class="search-wrap">
                <el-input class="search"
                          :placeholder="$t('graph.inputNodeName')"
                          v-model="searchBox.value"
                          @input="filterChange"
                          @keyup.enter.native="searchNodesNames"
                          clearable>
                  <el-button slot="append"
                             @click="treeWrapFlag=!treeWrapFlag"
                             class="collapse_i">
                    <i class="el-icon-caret-bottom"
                       v-if="!treeWrapFlag"></i>
                    <i class="el-icon-caret-top"
                       v-else></i>
                  </el-button>
                </el-input>
                <div class="tree-wrap"
                     v-show="treeWrapFlag">
                  <el-tree v-show="treeFlag"
                           :props="props"
                           :load="loadNode"
                           @node-collapse="nodeCollapse"
                           @node-click="handleNodeClick"
                           node-key="name"
                           :expand-on-click-node="false"
                           :lazy="true"
                           :highlight-current="true"
                           ref="tree">
                    <span class="custom-tree-node"
                          slot-scope="{ node ,data }">
                      <span>
                        <img v-if="data.type ==='name_scope'"
                             :src="require(`@/assets/images/${themeIndex}/name-scope.svg`)"
                             class="image-type" />
                        <img v-else-if="data.type ==='Const'"
                             :src="require(`@/assets/images/${themeIndex}/constant-node.svg`)"
                             class="image-type" />
                        <img v-else-if="data.type ==='aggregation_scope'"
                             :src="require(`@/assets/images/${themeIndex}/polymetric.svg`)"
                             class="image-type" />
                        <img v-else-if="nodeIsDynamicShape(node.data)"
                             :src="require('@/assets/images/dynamic-shape-node.svg')"
                             class="image-type" />
                        <img v-else
                             :src="require(`@/assets/images/${themeIndex}/operator-node.svg`)"
                             class="image-type" />
                      </span>
                      <span class="custom-tree-node">{{ node.label }}</span>
                    </span>
                  </el-tree>
                  <el-tree v-show="!treeFlag"
                           :props="defaultProps"
                           :load="loadSearchNode"
                           :lazy="true"
                           node-key="name"
                           @node-click="handleNodeClick"
                           :expand-on-click-node="false"
                           ref="searchTree">
                    <span class="custom-tree-node"
                          slot-scope="{ node ,data }">
                      <span>
                        <img v-if="data.type ==='name_scope'"
                             :src="require(`@/assets/images/${themeIndex}/name-scope.svg`)"
                             class="image-type" />
                        <img v-else-if="data.type ==='Const'"
                             :src="require(`@/assets/images/${themeIndex}/constant-node.svg`)"
                             class="image-type" />
                        <img v-else-if="data.type ==='aggregation_scope'"
                             :src="require(`@/assets/images/${themeIndex}/polymetric.svg`)"
                             class="image-type" />
                        <img v-else-if="nodeIsDynamicShape(node.data)"
                             :src="require('@/assets/images/dynamic-shape-node.svg')"
                             class="image-type" />
                        <img v-else
                             :src="require(`@/assets/images/${themeIndex}/operator-node.svg`)"
                             class="image-type" />
                      </span>
                      <span class="custom-tree-node">{{ node.label }}</span>
                    </span>
                  </el-tree>
                </div>
              </div>
              <!-- Functional Area -->
              <div id="small-container">
                <div id="inside-box"
                     :style="styleSet(insideBox)"></div>
                <canvas ref="canvas"
                        :width="smallContainer.width"
                        :height="smallContainer.height"></canvas>
              </div>
              <!-- Node information -->
              <div :class="
                  showLegend
                    ? 'node-info-con node-info-container'
                    : 'node-info-con node-info-container-long'
                ">
                <div class="title">
                  {{ $t('graph.nodeInfo') }}
                  <img :src="require('@/assets/images/all-drop-down.png')"
                       fun="fold"
                       hidden
                       alt="" />
                  <img :src="require('@/assets/images/all-uptake.png')"
                       fun="fold"
                       hidden
                       alt="" />
                </div>
                <div class="node-info"
                     v-show="selectedNode.show">
                  <div class="items">
                    <div class="label item">{{ $t('graph.name') }}</div>
                    <div class="value">
                      <span class="cl-display-block"
                            @dblclick="nodeNameClick">{{ selectedNode.title }}</span>
                    </div>
                  </div>
                  <div class="items"
                       v-if="selectedNode.countShow">
                    <div class="label item">{{ $t('graph.count') }}</div>
                    <div class="value items-over">{{ selectedNode.count }}</div>
                  </div>
                  <div class="items"
                       v-if="!selectedNode.countShow">
                    <div class="label item">{{ $t('graph.type') }}</div>
                    <div class="value items-over">{{ selectedNode.type }}</div>
                  </div>
                  <div class="items itemHeight"
                       v-if="!selectedNode.countShow">
                    <div class="item">
                      {{ $t('graph.attr') }} ({{
                        selectedNode.info.attributes.length
                      }})
                    </div>
                  </div>
                  <ul v-if="selectedNode.info && !selectedNode.countShow"
                      class="item-content hover"
                      :class="
                      selectedNode.info.attributes.length > 2
                        ? 'item-min2'
                        : selectedNode.info.attributes.length > 0
                        ? 'item-min'
                        : ''
                    ">
                    <li v-for="item in selectedNode.info.attributes"
                        :key="item.name">
                      <div class="key">
                        {{ item.name }}
                      </div>
                      <div class="input cl-input-value">
                        <pre>{{ item.value }}</pre>
                      </div>
                    </li>
                  </ul>
                  <div class="items itemHeight">
                    <div class="item">
                      {{ $t('graph.inputs') }} (
                      {{
                        selectedNode.info.input.length +
                          selectedNode.info.inputControl.length
                      }})
                    </div>
                  </div>
                  <ul v-if="selectedNode.info"
                      class="item-content hover"
                      :class="
                      selectedNode.info.input.length > 1
                        ? 'item-min2'
                        : selectedNode.info.input.length > 0
                        ? 'item-min'
                        : ''
                    ">
                    <li v-for="item in selectedNode.info.input"
                        :key="item.$index"
                        @click="querySingleNode({ value: item.name })"
                        class="pointer">
                      <div class="input">{{ item.name }}</div>
                      <div class="size">{{ item.value }}</div>
                      <div class="clear"></div>
                    </li>
                    <li class="control-list"
                        v-if="
                        selectedNode.info &&
                          selectedNode.info.inputControl.length
                      ">
                      <div class="dependence-title"
                           @click="toggleControl('input')"
                           :class="selectedNode.showControl.input ? '' : 'hide'">
                        <img :src="require('@/assets/images/all-uptake.png')"
                             alt="" />
                        {{ $t('graph.controlDependencies') }}
                      </div>
                      <ul v-show="selectedNode.showControl.input">
                        <li v-for="item in selectedNode.info.inputControl"
                            :key="item.$index"
                            @click="querySingleNode({ value: item.name })"
                            class="pointer">
                          <div class="input">{{ item.name }}</div>
                          <div class="size">{{ item.value }}</div>
                          <div class="clear"></div>
                        </li>
                      </ul>
                    </li>
                  </ul>
                  <div class="items">
                    <div class="item">
                      {{ $t('graph.outputs') }} (
                      {{
                        selectedNode.info.output.length +
                          selectedNode.info.outputControl.length
                      }})
                    </div>
                  </div>
                  <ul v-if="selectedNode.info"
                      class="item-content hover"
                      :class="
                      selectedNode.info.output.length > 1
                        ? 'item-min2'
                        : selectedNode.info.output.length > 0
                        ? 'item-min'
                        : ''
                    ">
                    <li v-for="item in selectedNode.info.output"
                        :key="item.$index"
                        @click="querySingleNode({ value: item.name })"
                        class="pointer">
                      <div class="input">{{ item.name }}</div>
                      <div class="size">{{ item.value }}</div>
                      <div class="clear"></div>
                    </li>
                    <li class="control-list"
                        v-if="
                        selectedNode.info &&
                          selectedNode.info.outputControl.length
                      ">
                      <div class="dependence-title"
                           @click="toggleControl('output')"
                           :class="selectedNode.showControl.output ? '' : 'hide'">
                        <img :src="require('@/assets/images/all-uptake.png')"
                             alt="" />
                        {{ $t('graph.controlDependencies') }}
                      </div>
                      <ul v-show="selectedNode.showControl.output">
                        <li v-for="item in selectedNode.info.outputControl"
                            :key="item.$index"
                            @click="querySingleNode({ value: item.name })"
                            class="pointer">
                          <div class="input">{{ item.name }}</div>
                          <div class="size">{{ item.value }}</div>
                          <div class="clear"></div>
                        </li>
                      </ul>
                    </li>
                  </ul>
                  <div class="items"
                       v-if="selectedNode.info && selectedNode.info.output_i !== 0">
                    <div class="label item">{{ $t('graph.outputs_i') }}</div>
                    <span class="value">{{ selectedNode.info.output_i }}</span>
                  </div>
                </div>
              </div>
              <!-- Legend -->
              <div class="legend"
                   v-if="!fullScreen">
                <div class="title">
                  {{ $t('graph.legend') }}
                  <img :src="require('@/assets/images/all-drop-down.png')"
                       v-show="!showLegend"
                       @click="foldLegend"
                       alt="" />
                  <img :src="require('@/assets/images/all-uptake.png')"
                       v-show="showLegend"
                       @click="foldLegend"
                       alt="" />
                </div>
                <div v-show="showLegend"
                     class="legend-content">
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require(`@/assets/images/${themeIndex}/name-scope.svg`)"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.nameSpace')">
                      {{ $t('graph.nameSpace') }}
                    </div>
                  </div>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require(`@/assets/images/${themeIndex}/polymetric.svg`)"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.polymetric')">
                      {{ $t('graph.polymetric') }}
                    </div>
                  </div>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require(`@/assets/images/${themeIndex}/virtual-node.svg`)"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.virtualNode')">
                      {{ $t('graph.virtualNode') }}
                    </div>
                  </div>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require(`@/assets/images/${themeIndex}/operator-node.svg`)"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.operatorNode')">
                      {{ $t('graph.operatorNode') }}
                    </div>
                  </div>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require(`@/assets/images/${themeIndex}/constant-node.svg`)"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.constantNode')">
                      {{ $t('graph.constantNode') }}
                    </div>
                  </div>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require(`@/assets/images/${themeIndex}/dynamic-shape-node.svg`)"
                          alt="" />
                    </div>
                    <div class="legend-text"
                        :title="$t('graph.dynamicShapeNode')">
                      {{ $t('graph.dynamicShapeNode') }}
                    </div>
                  </div>
                  <br>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require('@/assets/images/data-flow.png')"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.dataFlowEdge')">
                      {{ $t('graph.dataFlowEdge') }}
                    </div>
                  </div>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require('@/assets/images/control-dep.png')"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.controllDepEdge')">
                      {{ $t('graph.controllDepEdge') }}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import CommonProperty from '@/common/common-property.js';
import RequestService from '@/services/request-service';
import { select, selectAll, zoom } from 'd3';
import 'd3-graphviz';
const d3 = { select, selectAll, zoom };
import commonGraph from '../../mixins/common-graph.vue';
import smallMap from '../../mixins/small-map.vue';

const NORMAL_MODE = 'normal';
const OPTIMIZE_MODE = 'optimize';

export default {
  mixins: [commonGraph, smallMap],
  data() {
    return {
      summaryPath: this.$route.query.summaryPath,
      // Information about the selected node
      selectedNode: {
        info: {
          inputControl: [],
          input: [],
          outputControl: [],
          output: [],
          attributes: [],
        },
        showControl: {
          input: true,
          output: true,
        },
      },
      // Training job id
      trainJobID: '',
      // Node search box
      searchBox: {
        value: '',
        suggestions: [],
      },
      fileSearchBox: {
        value: '',
        suggestions: [],
      },
      showLegend: true, // Display Legend
      // Controls whether the loading is displayed.
      loading: {
        show: false,
        info: '',
      },
      rightShow: true, // Check whether the right side bar is displayed.
      fullScreen: false, // Display Full Screen
      initOver: false,
      guide: {
        show: false,
        step: 1,
        top: '0%',
        left: '0%',
        content: [
          this.$t('graph.guideContent11'),
          this.$t('graph.guideContent12'),
          this.$t('graph.guideContent13'),
          this.$t('graph.guideContent14'),
        ],
        title: '',
      },
      language: '',
      defaultProps: {
        children: 'nodes',
        label: 'label',
        isLeaf: 'leaf',
      },
      treeFlag: true,
      props: {
        label: 'label',
        children: 'children',
        isLeaf: 'leaf',
      },
      node: null,
      resolve: null,
      treeWrapFlag: true,
      searchNode: null,
      searchResolve: null,
      isIntoView: true,
      themeIndex: this.$store.state.themeIndex,
      graphMode: NORMAL_MODE,
      OPTIMIZE_MODE,
      isOptimizeGraph: false,
    };
  },
  watch: {
    guide: {
      handler(newVal) {
        if (newVal.step === 1) {
          this.guide.top = '20%';
          this.guide.left = '48%';
        } else if (newVal.step === 2) {
          this.guide.top = '47%';
          this.guide.left = '62%';
        } else if (newVal.step === 3) {
          this.guide.top = '45%';
          this.guide.left = '52%';
        }
        this.guide.title = this.$t(`graph.guideTitle${newVal.step}`);
      },
      deep: true,
    },
  },
  mounted() {
    // Judging from the training job overview.
    if (!this.$route.query || !this.$route.query.train_id) {
      this.trainJobID = '';
      this.$message.error(this.$t('trainingDashboard.invalidId'));
      document.title = `${this.$t('graph.titleText')}-MindInsight`;
      return;
    }
    const showGuide = window.localStorage.getItem('graphShowGuide');
    if (!showGuide) {
      this.guide.show = true;
      window.localStorage.setItem('graphShowGuide', true);
    }

    this.pageKey = 'graph';
    this.trainJobID = this.$route.query.train_id;

    this.language = window.localStorage.getItem('milang');
    const languageList = ['zh-cn', 'en-us'];
    if (!this.language || !languageList.includes(this.language)) {
      this.language = languageList[0];
      window.localStorage.setItem('milang', this.language);
    }

    document.title = `${this.trainJobID}-${this.$t('graph.titleText')}-MindInsight`;
    window.onresize = () => {
      if (this.graph.dom) {
        this.$nextTick(() => {
          setTimeout(() => {
            this.initSmallMap();
          }, this.resizeDelay);
        });
      }
    };

    this.$nextTick(this.getDatavisualPlugins);
  },
  destroyed() {
    window.onresize = null;
  },
  methods: {
    /**
     * Execute at graph mode changed
     */
    handleGraphModeChange() {
      this.graphMode = this.isOptimizeGraph ? OPTIMIZE_MODE : NORMAL_MODE;
      this.treeFlag = true;
      this.searchBox.value = '';
      this.initOver = false;
      this.getDatavisualPlugins();
    },
    /**
     * Tree linkage with graph  Expand of current node
     * @param {Object} nodes Data of children of current node
     * @param {Object} name  The name of the current node
     */
    nodeExpandLinkage(nodes, name) {
      const curNodeData = nodes.map((val) => {
        return {
          label: val.name.split('/').pop(),
          ...val,
        };
      });
      const node = this.$refs.tree.getNode(name);
      curNodeData.forEach((val) => {
        this.$refs.tree.append(val, name);
      });
      node.childNodes.forEach((val) => {
        if (val.data.type !== 'name_scope' && val.data.type !== 'aggregation_scope') {
          val.isLeaf = true;
        }
      });
      node.expanded = true;
      node.loading = false;
      this.$refs.tree.setCurrentKey(name);
      this.$nextTick(() => {
        setTimeout(() => {
          const dom = document.querySelector('.el-tree-node.is-current.is-focusable');
          if (dom && this.rightShow) {
            const treeDom = document.querySelector('.tree-wrap');
            treeDom.scrollTop = dom.offsetTop;
          }
        }, 800);
      });
    },
    /**
     * Collapse node
     * @param {Object} _
     * @param {Object} node node data
     */
    nodeCollapse(_, node) {
      node.loaded = false;
      if (this.treeFlag) {
        this.dealDoubleClick(node.data.name, node.expanded);
      }
    },
    /**
     * Draw the tree
     * @param {Object} node tree root node
     * @param {Function} resolve callback function ,return next node data
     */
    loadNode(node, resolve) {
      if (node.level === 0) {
        node.childNodes = [];
        if (!this.node && !this.resolve) {
          this.node = node;
          this.resolve = resolve;
        }
      } else if (node.level >= 1) {
        this.isIntoView = false;
        this.loading.info = this.$t('graph.queryLoading');
        this.loading.show = true;
        setTimeout(() => {
          this.queryGraphData(node.data.name, resolve);
        }, 200);
      }
    },
    /**
     * Draw the tree
     * @param {Object} node tree root node
     * @param {Function} resolve callback function ,return next node data
     */
    loadSearchNode(node, resolve) {
      if (node.level === 0) {
        node.childNodes = [];
        if (!this.searchNode && !this.searchResolve) {
          this.searchNode = node;
          this.searchResolve = resolve;
        }
      } else if (node.level >= 1) {
        const params = {
          name: node.data.name,
          train_id: this.trainJobID,
          tag: this.fileSearchBox.value,
          mode: this.graphMode,
        };
        if (node.childNodes && node.childNodes.length) {
          node.expanded = true;
          node.loaded = true;
          node.loading = false;
          return;
        }
        RequestService.queryGraphData(params).then((res) => {
          if (res && res.data && res.data.nodes) {
            const data = res.data.nodes.map((val) => {
              return {
                label: val.name.split('/').pop(),
                leaf: val.type === 'name_scope' || val.type === 'aggregation_scope' ? false : true,
                ...val,
              };
            });
            resolve(data);
          }
        });
      }
    },
    /**
     * check node is dynamic shape
     * @param {Object} node tree node
     */
    nodeIsDynamicShape(node) {
      const sig = node.is_dynamic_shape_node;
      return sig;
    },
    /**
     * Deal search data
     * @param {Array} arr search tree data
     */
    dealSearchResult(arr) {
      arr.forEach((val) => {
        if (val.nodes) {
          this.dealSearchResult(val.nodes);
        }
        val.label = val.name.split('/').pop();
      });
    },
    filterChange() {
      if (this.searchBox.value === '') {
        this.treeFlag = true;
        this.$nextTick(() => {
          setTimeout(() => {
            const dom = document.querySelector('.el-tree-node.is-current.is-focusable');
            if (dom && this.rightShow) {
              const treeDom = document.querySelector('.tree-wrap');
              treeDom.scrollTop = dom.offsetTop;
            }
          }, 800);
        });
      }
    },
    handleNodeClick(data) {
      this.isIntoView = false;
      this.selectedNode.name = data.name;
      if (this.treeFlag) {
        this.selectNode(true);
      } else {
        this.querySingleNode({ value: data.name });
      }
    },

    /**
     * Initialization method executed after the graph rendering is complete
     */
    startApp() {
      this.$nextTick(() => {
        this.loading.show = false;
      });
      this.initSmallMap();
      this.initZooming();
      if (this.selectedNode.name) {
        this.selectNode(true);
      }
    },
    /**
     * Double-click the processing to be performed on the node to expand or narrow the namespace or aggregation node.
     * @param {String} name Name of the current node (also the ID of the node)
     * @param {Boolean} expanded The expanded state of the current node
     */
    dealDoubleClick(name, expanded = false) {
      name = name.replace('_unfold', '');
      if (!(name && this.allGraphData[name])) return;
      this.loading.info = this.$t('graph.queryLoading');
      this.loading.show = true;
      this.$nextTick(() => {
        // DOM tree needs time to respond, otherwise the loading icon will not be displayed
        const timeOut = 500;
        setTimeout(() => {
          if (this.allGraphData[name].isUnfold) {
            this.selectedNode.name = name;
            this.deleteNamespace(name);
          } else if (!expanded) {
            this.queryGraphData(name);
          } else {
            this.loading.show = false;
          }
        }, timeOut);
      });
    },
    /**
     * To obtain graph data, initialize and expand the namespace or aggregate nodes.
     * @param {String} name Name of the current node.
     * @param {Function} resolve Callback function.
     */
    queryGraphData(name, resolve) {
      const namescopeChildLimit = 3500;
      const independentLayout = this.allGraphData[name] ? this.allGraphData[name].independent_layout : false;
      const params = {
        name: name,
        train_id: this.trainJobID,
        tag: this.fileSearchBox.value,
        mode: this.graphMode,
      };
      this.loading.info = this.$t('graph.queryLoading');
      this.loading.show = true;
      RequestService.queryGraphData(params)
        .then(
          (response) => {
            if (response && response.data && response.data.nodes) {
              // If the namespace to be expanded is larger than the maximum number of subnodes,
              // an error is reported and the namespace is highlighted.
              const nodesCountLimit = name ? this.nodesCountLimit : namescopeChildLimit;
              if (!independentLayout && response.data.nodes.length > nodesCountLimit) {
                this.$message.error(this.$t('graph.tooManyNodes'));
                this.packageDataToObject(name, false);
                this.loading.show = false;
                this.$refs.tree.getNode(name).loading = false;
              } else {
                const nodes = JSON.parse(JSON.stringify(response.data.nodes));
                if (nodes && nodes.length) {
                  this.packageDataToObject(name, true, nodes);
                  // If the name is empty, it indicates the outermost layer.
                  if (!name) {
                    this.initGraph();
                  } else {
                    if (this.allGraphData[name].type === 'aggregation_scope') {
                      this.dealAggregationNodes(name);
                      if (this.allGraphData[name].maxChainNum > this.maxChainNum) {
                        this.$message.error(this.$t('graph.tooManyChain'));
                        this.allGraphData[name].isUnfold = true;
                        this.selectedNode.name = name;
                        this.loading.show = false;
                        this.deleteNamespace(name);
                        this.$refs.tree.getNode(name).loading = false;
                        return;
                      }
                    }
                    this.allGraphData[name].isUnfold = true;
                    this.selectedNode.name = `${name}_unfold`;
                    this.layoutNamescope(name, true);
                  }
                } else {
                  this.initSmallMap();
                  this.loading.show = false;
                }
                const data = response.data.nodes.map((val) => {
                  return {
                    label: val.name.split('/').pop(),
                    leaf: val.type === 'name_scope' || val.type === 'aggregation_scope' ? false : true,
                    ...val,
                  };
                });
                if (name) {
                  if (resolve) {
                    resolve(JSON.parse(JSON.stringify(data)));
                  } else {
                    this.nodeExpandLinkage(response.data.nodes, name);
                  }
                } else {
                  this.node.childNodes = [];
                  this.resolve(JSON.parse(JSON.stringify(data)));
                }
              }
            }
          },
          (error) => {
            this.loading.show = false;
          }
        )
        .catch((error) => {
          // A non-Google Chrome browser may not work properly.
          this.loading.show = false;
          if (error && error.includes('larger than maximum 65535 allowed')) {
            this.$message.error(this.$t('graph.dataTooLarge'));
          } else {
            this.$bus.$emit('showWarmText', true);
          }
          if (name && this.allGraphData[name]) {
            this.allGraphData[name].isUnfold = false;
            this.allGraphData[name].children = [];
            this.allGraphData[name].size = [];
            this.allGraphData[name].html = '';
          }
        });
    },
    /**
     * To obtain datavisual plugins
     */
    getDatavisualPlugins() {
      const params = {
        mode: this.graphMode,
        train_id: this.trainJobID,
      };
      RequestService.getDatavisualPlugins(params)
        .then((res) => {
          this.fileSearchBox.suggestions = [];
          if (!res || !res.data || !res.data.plugins || !res.data.plugins.graph || !res.data.plugins.graph.length) {
            this.initOver = true;
            return;
          }
          const tags = this.graphMode === NORMAL_MODE ? res.data.plugins.graph : res.data.plugins.optimized_graph;
          let hasFileSearchValue = false;
          tags.forEach((k) => {
            this.fileSearchBox.suggestions.push({
              value: k,
            });
            hasFileSearchValue = k === this.fileSearchBox.value || hasFileSearchValue;
          });
          if (!this.initOver) {
            this.initOver = true;
            this.fileSearchBox.value = tags.length ? tags[0] : '';
            this.queryGraphData();
          } else if (!hasFileSearchValue) {
            this.fileSearchBox.value = '';
          }
        })
        .catch(() => {
          this.fileSearchBox.suggestions = [];
          this.initOver = true;
          this.loading.show = false;
        });
    },
    /**
     * Close the expanded namespace.
     * @param {String} name The name of the namespace to be closed.
     */
    deleteNamespace(name) {
      this.loading.info = this.$t('graph.searchLoading');
      this.loading.show = true;
      if (!this.selectedNode.more) {
        this.packageDataToObject(name, false);
        this.layoutController(name);
      } else {
        this.allGraphData[name].isUnfold = true;
        this.selectedNode.name = `${name}_unfold`;
        this.layoutNamescope(name, true);
      }
    },
    /**
     * Selecting a node
     * @param {Boolean} needFocus Whether to focus on the node
     */
    selectNode(needFocus = false) {
      window.getSelection().removeAllRanges();
      d3.selectAll('.node polygon, .node ellipse, .node rect, .node path').classed('selected', false);
      const path = this.selectedNode.name.split('^');
      const node = {};
      let id = path[0].replace('_unfold', '');
      id = this.allGraphData[id] && this.allGraphData[id].isUnfold ? `${id}_unfold` : id;
      node.eld3 = d3.select(`#graph g[id="${id}"]`);
      node.el = node.eld3.node();
      this.graph.dom.style.transition = '';

      const needDelay = path.length > 1;
      if ((needFocus || needDelay) && node.el) {
        this.selectNodePosition(id, needDelay);
      }
      node.eld3.select('polygon, rect, ellipse, path').classed('selected', true);
      this.highlightProxyNodes(id.replace('_unfold', ''));
      this.$refs.tree.setCurrentKey(id.replace('_unfold', ''));
      if (this.isIntoView) {
        this.$nextTick(() => {
          setTimeout(() => {
            const dom = document.querySelector('.el-tree-node.is-current.is-focusable');
            if (dom && this.rightShow) {
              const treeDom = document.querySelector('.tree-wrap');
              treeDom.scrollTop = dom.offsetTop;
            }
          }, 800);
        });
      }
      this.isIntoView = true;
      this.setNodeData();
    },
    /**
     * The node information of the selected node is displayed and highlighted.
     */
    setNodeData() {
      this.selectedNode.info = {
        input: [],
        inputControl: [],
        output: [],
        outputControl: [],
        attributes: [],
      };
      this.selectedNode.showControl = {
        input: true,
        output: true,
      };

      const path = this.selectedNode.name.split('^');
      const selectedNode = this.allGraphData[path[0].replace('_unfold', '')];

      if (selectedNode && !selectedNode.name.includes('more...')) {
        this.selectedNode.show = true;
        this.selectedNode.name = selectedNode.name;
        this.selectedNode.title = selectedNode.name.replace('_unfold', '');
        this.selectedNode.type =
          selectedNode.type === 'name_scope' || selectedNode.type === 'aggregation_scope' ? '' : selectedNode.type;
        if (selectedNode.is_dynamic_shape_node) {
          this.selectedNode.type = "dynamic-shape-node";
        }
        this.selectedNode.countShow = selectedNode.type === 'name_scope' || selectedNode.type === 'aggregation_scope';
        this.selectedNode.count = selectedNode.subnode_count;
        const attrTemp = JSON.parse(JSON.stringify(selectedNode.attr || {}));
        if (attrTemp.shape) {
          const shape = JSON.parse(attrTemp.shape);
          if (shape.length) {
            let str = '';
            for (let i = 0; i < shape.length; i++) {
              str += (str ? ',' : '') + JSON.stringify(shape[i]);
            }
            attrTemp.shape = str;
          }
        }

        this.selectedNode.info.attributes = Object.keys(attrTemp).map((key) => {
          return {
            name: key,
            value: attrTemp[key],
          };
        });

        Object.keys(selectedNode.input).forEach((key) => {
          const value = this.getEdgeLabel(selectedNode.input[key]);
          if (selectedNode.input[key].edge_type !== 'control') {
            this.selectedNode.info.input.push({
              name: key,
              value: value,
            });
          } else {
            this.selectedNode.info.inputControl.push({
              name: key,
              value: value,
            });
          }
        });

        Object.keys(selectedNode.output).forEach((key) => {
          const value = this.getEdgeLabel(selectedNode.output[key]);
          if (selectedNode.output[key].edge_type !== 'control') {
            this.selectedNode.info.output.push({
              name: key,
              scope: selectedNode.output[key].scope,
              value: value,
            });
          } else {
            this.selectedNode.info.outputControl.push({
              name: key,
              scope: selectedNode.output[key].scope,
              value: value,
            });
          }
        });
        this.selectedNode.info.output_i = selectedNode.output_i;
        this.highLightEdges(selectedNode);
      } else {
        this.selectedNode.show = false;
        this.selectedNode.name = '';
        this.selectedNode.title = '';
        this.selectedNode.type = '';
      }
    },
    /**
     * The position is offset to the current node in the center of the screen.
     * @param {String} nodeId Selected Node id
     * @param {Boolean} needDelay Delay required
     */
    selectNodePosition(nodeId, needDelay) {
      const nodeDom = document.querySelector(`#graph0 g[id="${nodeId}"]`);
      const nodeRect = nodeDom.getBoundingClientRect();

      const graph = {};
      graph.rect = this.graph.dom.getBoundingClientRect();
      graph.initWidth = graph.rect.width / this.graph.transform.k;
      graph.initHeight = graph.rect.height / this.graph.transform.k;

      const screenChange = {
        x: nodeRect.left + nodeRect.width / 2 - (this.svg.size.left + this.svg.size.width / 2),
        y: nodeRect.top + nodeRect.height / 2 - (this.svg.size.top + this.svg.size.height / 2),
      };

      this.graph.transform.x -= screenChange.x * (this.graph.size.width / graph.initWidth);
      this.graph.transform.y -= screenChange.y * (this.graph.size.height / graph.initHeight);

      this.graph.dom.setAttribute(
        'transform',
        `translate(${this.graph.transform.x},${this.graph.transform.y}) scale(${this.graph.transform.k})`
      );

      const transitionTime = Math.min(
        Math.abs(screenChange.x) * 2,
        Math.abs(screenChange.y) * 2,
        needDelay ? 800 : 0
      );

      this.graph.dom.style.transition = `${transitionTime / 1000}s`;
      this.graph.dom.style['transition-timing-function'] = 'linear';

      setTimeout(() => {
        this.graph.dom.style.transition = '';
      }, transitionTime);
      let end = 0;
      this.setInsideBoxData();
      const timer = setInterval(() => {
        this.setInsideBoxData();
        end += 1;
        if (end > transitionTime) {
          clearInterval(timer);
        }
      }, 1);
    },
    /**
     * The drop-down list box of the search drop-down list box is controlled.
     * @param {Object} event Operation event of a component.
     */
    selectBoxVisibleTriggle(event) {
      setTimeout(() => {
        document.querySelector('.el-autocomplete-suggestion').style.display =
          event.type === 'blur' ? 'none' : 'block';
      }, 300);
    },
    /**
     * file select change
     */
    fileChange() {
      this.selectedNode = {
        info: {
          inputControl: [],
          input: [],
          outputControl: [],
          output: [],
          attributes: [],
        },
        showControl: {
          input: true,
          output: true,
        },
      };
      this.clickScope = {};
      this.searchBox.value = '';
      Object.keys(this.allGraphData).forEach((key) => {
        delete this.allGraphData[key];
      });
      d3.select('#graph svg').remove();
      this.firstFloorNodes = [];
      this.queryGraphData();
      this.treeFlag = true;
    },
    /**
     * refresh select list
     * @param {Boolean} expanded Should get data visual plugins or not.
     */
    getSelectList(expanded) {
      if (expanded) {
        this.getDatavisualPlugins();
      }
    },
    /**
     * The search drop-down list box displays the matched data by entering data.
     * @param {String} content Input parameters
     * @param {Object} callback Callback Function
     */
    searchNodesNames() {
      this.searchBox.value = this.searchBox.value.trim();
      const params = {
        search: this.searchBox.value,
        train_id: this.trainJobID,
        tag: this.fileSearchBox.value,
        offset: 0,
        limit: 1000,
        mode: this.graphMode,
      };
      RequestService.searchNodesNames(params)
        .then(
          (response) => {
            if (response && response.data) {
              this.treeFlag = false;
              this.treeWrapFlag = true;
              this.searchNode.childNodes = [];
              const data = response.data.nodes.map((val) => {
                return {
                  label: val.name.split('/').pop(),
                  ...val,
                };
              });
              const currentData = JSON.parse(JSON.stringify(data));
              currentData.forEach((val) => {
                val.nodes = [];
              });
              this.searchResolve(currentData);
              data.forEach((val, key) => {
                if (val.nodes && val.nodes.length) {
                  val.nodes.forEach((value) => {
                    value.parentName = val.name;
                  });
                  this.dealSearchTreeData(val.nodes);
                }
              });
            }
          },
          (e) => {
            this.loading.show = false;
          }
        )
        .catch((e) => {
          this.$message.error(this.$t('public.dataError'));
        });
    },
    /**
     * Draw the tree
     * @param {Object} children child node
     */
    dealSearchTreeData(children) {
      children.forEach((val) => {
        const node = this.$refs.searchTree.getNode(val.parentName);
        val.label = val.name.split('/').pop();
        val.leaf = val.type === 'name_scope' || val.type === 'aggregation_scope' ? false : true;
        this.$refs.searchTree.append(val, node);
        node.expanded = true;
        if (val.nodes && val.nodes.length) {
          val.nodes.forEach((value) => {
            value.parentName = val.name;
          });
          this.dealSearchTreeData(val.nodes);
        }
      });
    },
    /**
     * Search for all data of a specific node and its namespace.
     * @param {Object} option Selected node data object
     */
    querySingleNode(option) {
      this.selectedNode.name = option.value;
      this.selectedNode.more = false;
      // If a node exists on the map, select the node.
      if (this.allGraphData[option.value]) {
        if (d3.select(`g[id="${option.value}"], g[id="${option.value}_unfold"]`).size()) {
          // If the namespace or aggregation node is expanded, you need to close it and select
          if (!this.allGraphData[option.value].isUnfold) {
            this.selectNode(true);
          } else {
            this.$refs.tree.getNode(option.value).expanded = false;
            this.dealDoubleClick(option.value);
          }
        } else {
          const parentId = option.value.substring(0, option.value.lastIndexOf('/'));
          if (this.allGraphData[parentId] && this.allGraphData[parentId].isUnfold) {
            const aggregationNode = this.allGraphData[parentId];
            if (aggregationNode && aggregationNode.childIdsList) {
              for (let i = 0; i < aggregationNode.childIdsList.length; i++) {
                if (aggregationNode.childIdsList[i].includes(option.value)) {
                  aggregationNode.index = i;
                  break;
                }
              }
            }
            this.loading.info = this.$t('graph.searchLoading');
            this.loading.show = true;
            this.selectedNode.name = option.value;

            this.$nextTick(() => {
              setTimeout(() => {
                this.layoutNamescope(parentId, true);
              }, 500);
            });
          }
        }
      } else {
        // If the node does not exist and is not a subnode of the aggregation node,
        // directly invoke the background for query.
        // If the node does not exist and is a subnode of the aggregation node, and the aggregation node is not
        // expanded, directly invoke the background to check the node.
        // If the node does not exist and is a child node in the aggregation node,
        // and the aggregation node is expanded but is not displayed on the diagram, you need to zoom out the
        // aggregated node, query the aggregation node again, and intercept the node array again.
        const params = {
          name: option.value,
          train_id: this.trainJobID,
          mode: this.graphMode,
          tag: this.fileSearchBox.value,
        };
        this.loading.info = this.$t('graph.searchLoading');
        this.loading.show = true;
        RequestService.querySingleNode(params)
          .then(
            (response) => {
              if (response && response.data && response.data.children) {
                const data = this.findStartUnfoldNode(response.data.children);
                if (data) {
                  this.dealAutoUnfoldNamescopesData(data);
                }
                if (response.data.children) {
                  this.dealTreeData(response.data.children, option.value);
                }
              }
            },
            (e) => {
              this.loading.show = false;
            }
          )
          .catch((e) => {
            this.loading.show = false;
            this.$message.error(this.$t('public.dataError'));
          });
      }
    },
    /**
     * Draw the tree
     * @param {Object} children child node
     * @param {String} name The name of the node that needs to be highlighted
     */
    dealTreeData(children, name) {
      if (children.nodes) {
        if (
          (children.nodes.length > this.nodesCountLimit &&
            this.$refs.tree.getNode(children.scope_name).data.type === 'name_scope') ||
          this.allGraphData[children.scope_name].maxChainNum > this.maxChainNum
        ) {
          return;
        }
        const data = children.nodes.map((val) => {
          return {
            label: val.name.split('/').pop(),
            ...val,
          };
        });
        data.forEach((val) => {
          const node = this.$refs.tree.getNode(children.scope_name);
          if (node.childNodes) {
            if (node.childNodes.map((value) => value.data.name).indexOf(val.name) === -1) {
              this.$refs.tree.append(val, node);
            }
          } else {
            this.$refs.tree.append(val, node);
          }
        });
        const node = this.$refs.tree.getNode(children.scope_name);
        node.childNodes.forEach((val) => {
          if (val.data.type !== 'name_scope' && val.data.type !== 'aggregation_scope') {
            val.isLeaf = true;
          }
        });
        node.expanded = true;
        node.loading = false;
      } else {
        this.$refs.tree.setCurrentKey(name);
        this.$nextTick(() => {
          setTimeout(() => {
            const dom = document.querySelector('.el-tree-node.is-current.is-focusable');
            if (dom && this.rightShow) {
              const treeDom = document.querySelector('.tree-wrap');
              treeDom.scrollTop = dom.offsetTop;
            }
          }, 800);
        });
      }
      if (children.children && Object.keys(children.children).length) {
        this.dealTreeData(children.children, name);
      }
    },
    /**
     * Processes all data of the queried node and the namespace to which the node belongs.
     * @param {Object} data All data of the node and the namespace to which the node belongs
     * @return {Object} The data object of the namespace to expand.
     */
    dealAutoUnfoldNamescopesData(data) {
      if (!data.scope_name) {
        return this.dealAutoUnfoldNamescopesData(data.children);
      } else {
        if (this.allGraphData[data.scope_name] && this.allGraphData[data.scope_name].isUnfold) {
          return this.dealAutoUnfoldNamescopesData(data.children);
        } else {
          // If the namespace is a namespace and the number of subnodes exceeds the upper limit,
          // an error is reported and the namespace is selected.
          if (this.allGraphData[data.scope_name].type === 'name_scope' && data.nodes.length > this.nodesCountLimit) {
            this.selectedNode.name = data.scope_name;
            this.querySingleNode({ value: data.scope_name });
            this.$message.error(this.$t('graph.tooManyNodes'));
            this.$nextTick(() => {
              this.loading.show = false;
            });
          } else {
            // Normal expansion
            const nodes = JSON.parse(JSON.stringify(data.nodes));
            this.packageDataToObject(data.scope_name, true, nodes);
            if (this.allGraphData[data.scope_name].type === 'aggregation_scope') {
              this.dealAggregationNodes(data.scope_name);
              const aggregationNode = this.allGraphData[data.scope_name];
              if (aggregationNode) {
                for (let i = 0; i < aggregationNode.childIdsList.length; i++) {
                  if (aggregationNode.childIdsList[i].includes(this.selectedNode.name)) {
                    aggregationNode.index = i;
                    break;
                  }
                }
              }
              if (this.allGraphData[data.scope_name].maxChainNum > this.maxChainNum) {
                this.selectedNode.name = data.scope_name;
                this.allGraphData[data.scope_name].isUnfold = false;
                this.deleteNamespace(data.scope_name);
                this.$message.error(this.$t('graph.tooManyChain'));
                this.$nextTick(() => {
                  this.loading.show = false;
                });
                return;
              }
            }

            if (data.children.scope_name) {
              this.dealAutoUnfoldNamescopesData(data.children);
            } else {
              this.loading.info = this.$t('graph.searchLoading');
              this.loading.show = true;
              this.$nextTick(() => {
                setTimeout(() => {
                  this.layoutNamescope(data.scope_name, true);
                }, 200);
              });
            }
          }
        }
      }
    },
    /**
     * Expansion and folding of control edges
     * @param {String} item Determines the control edge of the input or output.
     */
    toggleControl(item) {
      this.selectedNode.showControl[item] = !this.selectedNode.showControl[item];
    },
    /**
     * Click the node information name.
     */
    nodeNameClick() {
      const nodeNameText = event.target;
      if (document.body.createTextRange) {
        const nodeNameTextRange = document.body.createTextRange();
        nodeNameTextRange.moveToElementText(nodeNameText);
        nodeNameTextRange.select();
      } else if (window.getSelection) {
        const nodeNameSelection = window.getSelection();
        const nodeNameTextRange = document.createRange();
        nodeNameTextRange.selectNodeContents(nodeNameText);
        nodeNameSelection.removeAllRanges();
        nodeNameSelection.addRange(nodeNameTextRange);
      }
    },
    /**
     * Collapse on the right
     */
    toggleRight() {
      this.rightShow = !this.rightShow;
      setTimeout(this.initSmallMap, this.resizeDelay);
    },
    /**
     * Full-screen display
     */
    toggleScreen() {
      this.fullScreen = !this.fullScreen;
      setTimeout(this.initSmallMap, this.resizeDelay);
    },
    /**
     * Download svg
     */
    downLoadSVG() {
      const svgXml = document.querySelector('#graph #graph0').innerHTML;
      const bbox = document.getElementById('graph0').getBBox();
      const viewBoxSize = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
      const encodeStr =
        `<svg xmlns="http://www.w3.org/2000/svg" ` +
        `xmlns:xlink="http://www.w3.org/1999/xlink" ` +
        `width="${bbox.width}" height="${bbox.height}" ` +
        `viewBox="${viewBoxSize}">${
          CommonProperty.graphDownloadStyle[this.$store.state.themeIndex]
        }<g>${svgXml}</g></svg>`;

      const downloadLink = document.createElement('a');
      downloadLink.download = 'graph.svg';
      downloadLink.style.display = 'none';
      const blob = new Blob([encodeStr], { type: 'text/html' });
      downloadLink.href = URL.createObjectURL(blob);
      document.body.appendChild(downloadLink);
      downloadLink.click();
      document.body.removeChild(downloadLink);
    },
    /**
     * Fold the legend area.
     */
    foldLegend() {
      this.showLegend = !this.showLegend;
    },
    /**
     * Control Display User Guide
     */
    showUserGuide() {
      this.guide.content = [
        this.$t('graph.guideContent11'),
        this.$t('graph.guideContent12'),
        this.$t('graph.guideContent13'),
        this.$t('graph.guideContent14'),
      ];
      this.guide.show = true;
      this.guide.step = 1;
    },
    /**
     * Close user guide
     */
    closeUserGuide() {
      this.guide.show = false;
    },
    /**
     * Show the next step
     */
    guideNext() {
      if (this.guide.step < 3) {
        this.guide.step++;
        switch (this.guide.step) {
          case 2:
            this.guide.content = [this.$t('graph.guideContent2')];
            break;
          case 3:
            this.guide.content = [this.$t('graph.guideContent3')];
            break;
          default:
            break;
        }
      } else if (this.guide.step >= 3) {
        this.guide.show = false;
      }
    },
    /**
     * jump back to train dashboard
     */
    jumpToTrainDashboard() {
      this.$router.push({
        path: '/train-manage/training-dashboard',
        query: {
          id: this.trainJobID,
        },
      });
    },
  },
  // Components imported by the page
  components: {},
};
</script>
<style>
.tooltip-container .cl-graph-sidebar-tip {
  word-break: normal;
}

.cl-graph-manage {
  height: 100%;
}
.cl-graph-manage .cl-graph-title {
  height: 50px;
  line-height: 50px;
}
.cl-graph-manage .cl-graph-title .path-message {
  display: inline-block;
  line-height: 20px;
  padding: 0px 4px 15px 4px;
  font-weight: bold;
  vertical-align: bottom;
}
.cl-graph-manage .cl-graph-title .guide {
  cursor: pointer;
  margin-left: 10px;
  display: inline-block;
  line-height: 18px;
  font-size: 12px;
}
.cl-graph-manage .cl-graph-title .guide .guide-icon {
  display: inline-block;
  width: 16px;
  height: 16px;
  vertical-align: -2.5px;
  margin-right: 4px;
}
.cl-graph-manage .cl-graph-title .guide .guide-icon-0 {
  background: url('../../assets/images/0/guideIcon.svg');
}
.cl-graph-manage .cl-graph-title .guide .guide-icon-1 {
  background: url('../../assets/images/1/guideIcon.svg');
}
.cl-graph-manage .cl-graph-title .guide:hover {
  color: var(--theme-color);
}
.cl-graph-manage .cl-graph-title .guide:hover .guide-icon-0 {
  background: url('../../assets/images/0/guideIconHover.svg');
}
.cl-graph-manage .cl-graph-title .guide:hover .guide-icon-1 {
  background: url('../../assets/images/1/guideIconHover.svg');
}
.cl-graph-manage .graph-p32 {
  height: 100%;
  position: relative;
}
.cl-graph-manage .graph-p32 .guide-content {
  height: 100%;
  width: 100%;
  position: absolute;
  background-color: var(--graph-guide-content-bg-color);
  z-index: 999;
}
.cl-graph-manage .graph-p32 .guide-content .step-pic {
  text-align: center;
  margin-top: 8px;
}
.cl-graph-manage .graph-p32 .guide-content .step {
  height: 100%;
  background-repeat: round;
  user-select: none;
}
.cl-graph-manage .graph-p32 .guide-content .step img {
  width: 100%;
}
.cl-graph-manage .graph-p32 .guide-content .guide-span {
  font-size: 12px;
  line-height: 18px;
  text-align: left;
  display: inline-block;
}
.cl-graph-manage .graph-p32 .guide-content .el-popover .el-icon-close {
  cursor: pointer;
  position: absolute;
  right: 10px;
  top: 13px;
  font-size: 20px;
}
.cl-graph-manage .graph-p32 .guide-content .el-popover .el-icon-close:hover {
  color: var(--theme-color);
}
.cl-graph-manage .graph-p32 .guide-content .el-popover__title {
  font-size: 16px;
  line-height: 24px;
  font-weight: bold;
}
.cl-graph-manage .graph-p32 .guide-content .el-button {
  display: block;
  float: right;
  height: 28px;
  line-height: 27px;
  border-radius: 0;
  padding: 0 20px;
}
.cl-graph-manage .cl-content {
  height: calc(100% - 50px);
  overflow: auto;
}
.cl-graph-manage #graphs {
  width: 100%;
  height: 100%;
  font-size: 0;
  background: #f0f2f5;
}
.cl-graph-manage #graphs .search {
  margin-bottom: 12px;
  width: 100%;
}
.cl-graph-manage #graphs .search-wrap {
  position: relative;
}
.cl-graph-manage #graphs .search-wrap .tree-wrap {
  position: absolute;
  left: 0;
  top: 32px;
  z-index: 101;
  width: 100%;
  max-height: 224px;
  overflow: auto;
  border: 1px solid var(--table-border-color);
  border-top: none;
  background: #fff;
}
.cl-graph-manage #graphs .search-wrap .tree-wrap .image-type {
  width: 20px;
  height: 10px;
  margin-right: 10px;
}
.cl-graph-manage #graphs .search-wrap .tree-wrap .el-tree > .el-tree-node {
  min-width: 100%;
  display: inline-block;
}
.cl-graph-manage #graphs .search-wrap .tree-wrap .el-tree .custom-tree-node {
  padding-right: 8px;
}
.cl-graph-manage #graphs .search-wrap .collapse_i {
  cursor: pointer;
}
.cl-graph-manage #graphs .cl-graph {
  position: relative;
  width: 100%;
  height: 100%;
  background-color: var(--bg-color);
  padding: 0 32px 10px;
  min-height: 700px;
  overflow: hidden;
}
.cl-graph-manage #graphs .cl-graph.full-screen {
  position: absolute;
  top: 0;
  bottom: 0;
  left: -280px;
  right: 0;
  width: auto;
  height: auto;
  padding: 0;
}
.cl-graph-manage #graphs .cl-graph.full-screen #sidebar .node-info-con {
  height: calc(100% - 300px);
}
.cl-graph-manage #graphs .cl-graph.full-screen .graph-container {
  width: 100%;
}
.cl-graph-manage #graphs #sidebar.right-hide {
  right: -442px;
}
.cl-graph-manage #graphs #sidebar {
  position: absolute;
  right: 0;
  top: 0;
  width: 442px;
  height: calc(100% - 15px);
  border-radius: 6px;
  text-align: left;
  background-color: var(--item-bg-color);
  display: inline-block;
  color: var(--font-color);
  font-size: 14px;
  line-height: 14px;
  padding: 18px 32px 10px;
  border: 1px solid var(--graph-right-module-border-color);
}
.cl-graph-manage #graphs #sidebar .graph-mode {
  display: flex;
  align-items: center;
  height: 32px;
  margin-bottom: 12px;
}
.cl-graph-manage #graphs #sidebar .graph-mode .select {
  margin-left: 16px;
  flex-grow: 1;
}
.cl-graph-manage #graphs #sidebar #small-container {
  height: 209px;
  width: 100%;
  z-index: 100;
  border: 1px solid var(--pagination-btn-color);
  overflow: hidden;
  background-color: var(--bg-color);
  position: relative;
}
.cl-graph-manage #graphs #sidebar .sidebar-tooltip {
  position: absolute;
  height: 62px;
  top: 2px;
  left: 10px;
  display: flex;
  align-items: center;
  font-size: 16px;
  color: #6c7280;
}
.cl-graph-manage #graphs #sidebar div,
.cl-graph-manage #graphs #sidebar span,
.cl-graph-manage #graphs #sidebar pre {
  font-size: 14px;
}
.cl-graph-manage #graphs #sidebar #small-container {
  height: 209px;
  width: 100%;
  z-index: 100;
  border: 1px solid var(--pagination-btn-color);
  overflow: hidden;
  background-color: var(--bg-color);
  position: relative;
}
.cl-graph-manage #graphs #sidebar #small-container #inside-box {
  background-color: #5b88f1;
  position: absolute;
  opacity: 0.3;
  width: 100%;
  height: 100%;
  left: 0px;
  top: 0px;
  z-index: 200;
  cursor: move;
}
.cl-graph-manage #graphs #sidebar .title {
  padding: 12px 0;
  font-size: 14px;
}
.cl-graph-manage #graphs #sidebar .title img {
  float: right;
  margin-right: 10px;
  cursor: pointer;
}
.cl-graph-manage #graphs #sidebar .graph-controls {
  padding: 10px 20px 0 20px;
}
.cl-graph-manage #graphs #sidebar .graph-controls div {
  cursor: pointer;
  display: inline-block;
  margin-left: 20px;
}
.cl-graph-manage #graphs #sidebar .graph-controls img {
  cursor: pointer;
  vertical-align: middle;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-button {
  z-index: 200;
  width: 10px;
  height: 10px;
  cursor: pointer;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-button:horizontal:single-button:start {
  background-image: url('../../assets/images/scroll-btn-left.png');
  background-position: center;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-button:horizontal:single-button:end {
  background-image: url('../../assets/images/scroll-btn-right.png');
  background-position: center;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-button:vertical:single-button:start {
  background-image: url('../../assets/images/scroll-btn-up.png');
  background-position: center;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-button:vertical:single-button:end {
  background-image: url('../../assets/images/scroll-btn-down.png');
  background-position: center;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-thumb {
  background-color: var(--graph-legend-scrollbar-thumb);
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
.cl-graph-manage #graphs #sidebar .node-info-container {
  height: calc(100% - 440px);
}
.cl-graph-manage #graphs #sidebar .node-info-container-long {
  height: calc(100% - 340px);
}
.cl-graph-manage #graphs #sidebar .node-info {
  font-size: 14px;
  padding: 0 20px;
  height: calc(100% - 54px);
  overflow: auto;
  color: var(--font-color);
  background-color: var(--graph-legend-bg-color);
}
.cl-graph-manage #graphs #sidebar .node-info .clear {
  clear: both;
}
.cl-graph-manage #graphs #sidebar .node-info .hover li:hover {
  background: var(--graph-legend-node-info-color);
}
.cl-graph-manage #graphs #sidebar .node-info .hover .control-list .dependence-title {
  line-height: 30px;
  cursor: pointer;
  font-weight: bold;
}
.cl-graph-manage #graphs #sidebar .node-info .hover .control-list .dependence-title img {
  vertical-align: middle;
  margin-right: 3px;
}
.cl-graph-manage #graphs #sidebar .node-info .hover .control-list .dependence-title.hide img {
  margin-top: -3px;
  transform: rotate(-90deg);
}
.cl-graph-manage #graphs #sidebar .node-info .hover .control-list li:hover {
  background: var(--graph-legend-node-info-color);
}
.cl-graph-manage #graphs #sidebar .node-info .hover .control-list:hover {
  background: none;
}
.cl-graph-manage #graphs #sidebar .node-info .pointer {
  cursor: pointer;
}
.cl-graph-manage #graphs #sidebar .node-info .item-content {
  max-height: calc(50% - 95px);
  overflow: auto;
}
.cl-graph-manage #graphs #sidebar .node-info .item-content li {
  min-width: 100%;
  width: max-content;
}
.cl-graph-manage #graphs #sidebar .node-info .item-min {
  min-height: 50px;
}
.cl-graph-manage #graphs #sidebar .node-info .item-min2 {
  min-height: 87px;
}
.cl-graph-manage #graphs #sidebar .node-info .items {
  line-height: 20px;
  padding: 9px 0;
}
.cl-graph-manage #graphs #sidebar .node-info .items .items-over {
  max-height: 60px;
  overflow: auto;
}
.cl-graph-manage #graphs #sidebar .node-info .items .item {
  color: #999;
}
.cl-graph-manage #graphs #sidebar .node-info .shape {
  vertical-align: top;
  width: 50px;
  word-break: break-all;
  display: inline-table;
  position: absolute;
  left: 0;
}
.cl-graph-manage #graphs #sidebar .node-info .key {
  vertical-align: top;
  width: 60px;
  word-break: break-all;
  display: inline-table;
}
.cl-graph-manage #graphs #sidebar .node-info .label {
  vertical-align: top;
  width: 70px;
  word-break: break-all;
  display: inline-block;
}
.cl-graph-manage #graphs #sidebar .node-info .value {
  vertical-align: top;
  display: inline-block;
  width: calc(100% - 70px);
  white-space: nowrap;
  overflow: auto;
}
.cl-graph-manage #graphs #sidebar .node-info .size {
  width: 310px;
  font-size: 12px;
  text-align: right;
}
.cl-graph-manage #graphs #sidebar .node-info .input {
  width: 100%;
  position: relative;
  display: inline-block;
  white-space: nowrap;
}
.cl-graph-manage #graphs #sidebar .node-info ul li {
  line-height: 20px;
}
.cl-graph-manage #graphs #sidebar .legend .legend-content {
  background-color: var(--graph-legend-bg-color);
  padding: 0 32px;
  height: 94px;
  overflow-y: auto;
  border: 1px solid var(--table-border-color);
}
.cl-graph-manage #graphs #sidebar .legend .legend-item {
  padding: 5px 0;
  display: inline-block;
  width: 50%;
  font-size: 14px;
  line-height: 20px;
}
.cl-graph-manage #graphs #sidebar .legend .legend-item .pic {
  width: 45px;
  text-align: center;
  display: inline-block;
  padding-left: 20px;
  vertical-align: middle;
}
.cl-graph-manage #graphs #sidebar .legend .legend-item .pic img {
  max-width: 45px;
  max-height: 15px;
  margin-left: -20px;
  vertical-align: middle;
}
.cl-graph-manage #graphs #sidebar .legend .legend-item .legend-text {
  display: inline-block;
  padding-left: 20px;
  width: calc(100% - 45px);
  vertical-align: middle;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cl-graph-manage #graphs #sidebar .legend .legend-item .legend-text:hover {
  cursor: default;
}
.cl-graph-manage #graphs #sidebar .toggle-right {
  position: absolute;
  top: calc(50% - 43px);
  left: -14px;
  width: 24px;
  height: 88px;
  cursor: pointer;
  transform: rotateY(180deg);
}
.cl-graph-manage #graphs #sidebar .toggle-right.toggle-0-btn {
  background-image: url('../../assets/images/0/collapse-left.svg');
}
.cl-graph-manage #graphs #sidebar .toggle-right.toggle-1-btn {
  background-image: url('../../assets/images/1/collapse-left.svg');
}
.cl-graph-manage #graphs #sidebar .toggle-right.toggle-left.toggle-0-btn {
  background-image: url('../../assets/images/0/collapse-right.svg');
}
.cl-graph-manage #graphs #sidebar .toggle-right.toggle-left.toggle-1-btn {
  background-image: url('../../assets/images/1/collapse-right.svg');
}
.cl-graph-manage #graphs .graph-mode-switch {
  position: absolute;
  left: 5px;
  top: 5px;
  z-index: 100;
}
.cl-graph-manage #graphs .graph-mode-switch-full-screen {
  position: absolute;
  left: 285px;
  top: 5px;
  z-index: 100;
}
.cl-graph-manage #graphs .operate-button-list {
  position: absolute;
  right: 0;
  top: 0;
  z-index: 100;
  display: flex;
}
.cl-graph-manage #graphs .operate-button-list div {
  cursor: pointer;
  width: 24px;
  height: 24px;
  margin: 0;
  display: inline-block;
  background-repeat: no-repeat;
  background-position: center;
}
.cl-graph-manage #graphs .operate-button-list .download-button {
  background-image: url('../../assets/images/download.png');
}
.cl-graph-manage #graphs .operate-button-list .full-screen-button {
  background-image: url('../../assets/images/full-screen.png');
}
.cl-graph-manage #graphs .graph-container.all {
  width: 100%;
}
.cl-graph-manage #graphs .graph-container .node:hover > path,
.cl-graph-manage #graphs .graph-container .node:hover > ellipse,
.cl-graph-manage #graphs .graph-container .node:hover > polygon,
.cl-graph-manage #graphs .graph-container .node:hover > rect {
  stroke-width: 2px;
}
.cl-graph-manage #graphs .graph-container .node.cluster > rect:hover {
  stroke: #8df1f2;
}
.cl-graph-manage #graphs .graph-container .selected {
  stroke: red !important;
  stroke-width: 2px;
}
.cl-graph-manage #graphs .graph-container {
  font-size: 16px;
  position: relative;
  display: inline-block;
  width: calc(100% - 442px);
  height: calc(100% - 5px);
  text-align: left;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
.cl-graph-manage #graphs .graph-container .graph {
  height: 100%;
  background-color: var(--graph-bg-color);
}
.cl-graph-manage #graphs .graph-container #graph0 > polygon {
  fill: transparent;
}
.cl-graph-manage #graphs .graph-container .node {
  cursor: pointer;
}
.cl-graph-manage #graphs .graph-container .edge path {
  stroke: #787878;
}
.cl-graph-manage #graphs .graph-container .edge polygon {
  fill: #787878;
}
.cl-graph-manage #graphs .graph-container .edge.highlighted path {
  stroke: red;
}
.cl-graph-manage #graphs .graph-container .edge.highlighted polygon {
  stroke: red;
  fill: red;
}
.cl-graph-manage #graphs .graph-container .edge.highlighted marker path {
  fill: red;
}
.cl-graph-manage #graphs .graph-container .node.aggregation > polygon {
  stroke: #e3aa00;
  fill: var(--graph-aggregation-color);
}
.cl-graph-manage #graphs .graph-container .node.dynamicShape > ellipse {
  stroke: #0080a0;
  fill: #00a055;
}
.cl-graph-manage #graphs .graph-container .node.cluster.aggregation > rect {
  stroke: #e3aa00;
  fill: #ffe794;
  stroke-dasharray: 3, 3;
}
.cl-graph-manage #graphs .graph-container .node > polygon {
  stroke: var(--theme-color);
  fill: var(--graph-polygon-color);
}
.cl-graph-manage #graphs .graph-container .node > ellipse {
  stroke: #4ea6e6;
  fill: var(--graph-operator-color);
}
.cl-graph-manage #graphs .graph-container .plain > path,
.cl-graph-manage #graphs .graph-container .plain ellipse {
  stroke: #e37d29;
  fill: var(--graph-plain-color);
  stroke-dasharray: 1.5, 1.5;
}
.cl-graph-manage #graphs .graph-container .edge-point ellipse {
  stroke: #a7a7a7;
  fill: #a7a7a7;
}
.cl-graph-manage #graphs .graph-container text {
  fill: var(--font-color);
}
.cl-graph-manage #graphs .image-noData {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  z-index: 200;
}
.cl-graph-manage #graphs .noData-text {
  margin-top: 33px;
  font-size: 18px;
}
.cl-graph-manage .cl-display-block {
  display: block;
}
.cl-graph-manage .cl-input-value {
  width: calc(100% - 70px) !important;
  margin-left: 10px !important;
}
.cl-graph-manage .cl-close-btn {
  width: 20px;
  height: 20px;
  vertical-align: -3px;
  cursor: pointer;
  display: inline-block;
}
.cl-graph-manage .cl-title-right {
  padding-right: 32px;
}
</style>
